/*LICENSE_BEGIN
 * ******************************************************************
 * All original parts of SoccerScope2003 have copyright (2003) by
 * Team YowAI, represented by Mr. Shuhei Shiota
 * The Department of Creative Informatics
 * The Graduate School of Information Science and Technology
 * The University of Tokyo
 * 
 * Additional classes (except libraries in /lib) were developed by 
 * the Dainamite Robocup-Team of the DAI-Labor, TU-Berlin 
 * (www.dai-labor.de)
 * 
 * Extended/new classes/packages are:
 *   soccerscope.analyzer.LogfileAnalyzer
 *   soccerscope.daistatistic.*
 *   soccerscope.daistatistic.charts.*
 *   soccerscope.daistatistic.tools.*
 *   soccerscope.db.*
 *   soccerscope.dialog.*
 *   soccerscope.model.PlayerWorldModel
 *   soccerscope.model.SimplePlayer
 *   soccerscope.net.AgentConnector
 *   soccerscope.training.*
 *   soccerscope.training.conditions.*
 * ******************************************************************
LICENSE_END*/
package soccerscope.training;

import java.util.Random;

import robocup.component.actions.Action;
import robocup.component.actions.CoachAction;
import robocup.component.geometry.Vektor;
import soccerscope.model.Team;
import soccerscope.util.geom.Point2f;

public class ObjectTrainingModel {

	public enum POSITION_TYPE {
		FIXED, RECTANGULAR, CIRCULAR
	}

	int id = -1;

	// training-data:
	// 1. position:
	private Point2f position = new Point2f();

	private float x_pos_diff = 0;

	private float y_pos_diff = 0;

	private POSITION_TYPE type = POSITION_TYPE.FIXED;

	// 2. speed - direction:
	private float speed_angle = 0;

	private float speed_angle_diff = 0;

	// 3. speed - length
	private int max_speed = 0;

	private int min_speed = 0;

	private Point2f observer = null;

	/**
	 * Constructor references the object to observe!
	 * 
	 * @param o -
	 *            a ball or player
	 */
	public ObjectTrainingModel(int id) {
		super();
		this.id = id;
	}

	/**
	 * This method retrieves the max-speed value in percent, ranging from 0-100.
	 * So, it depends on the maximum speed an object can have (e.g. 1.0 as
	 * player or 2.7 as ball)
	 * 
	 * @return the max-speed
	 */
	public int getMax_speed() {
		return this.max_speed;
	}

	/**
	 * This method sets the max-speed. It has to be greater or equal to
	 * min-speed and between 0 and 100, as it is a percentage value. It may
	 * modify the min-speed, so that the values will be consistent!
	 * 
	 * @param max_speed
	 *            the new max-speed [0..100]
	 */
	public void setMax_speed(int max_speed) {
		this.max_speed = Math.min(100, max_speed);
		this.max_speed = Math.max(0, max_speed);
		if (this.min_speed > this.max_speed)
			this.min_speed = this.max_speed;
	}

	/**
	 * This method retrieves the min-speed value in percent, ranging from 0-100.
	 * So, it depends on the speed an object can have (e.g. 1.0 as player or 2.7
	 * as ball)
	 * 
	 * @return the min-speed
	 */
	public int getMin_speed() {
		return this.min_speed;
	}

	/**
	 * This method sets the min-speed. It has to be smaller or equal to
	 * max-speed and between 0 and 100, as it is a percentage value. It may
	 * modify the max-speed, so that the values will be consistent!
	 * 
	 * @param min_speed
	 *            the new min-speed [0..100]
	 */
	public void setMin_speed(int min_speed) {
		this.min_speed = Math.min(100, min_speed);
		this.min_speed = Math.max(0, min_speed);
		if (this.max_speed < this.min_speed)
			this.max_speed = this.min_speed;
	}

	// public SoccerObject getObject() {
	// return object;
	// }
	//
	//
	// public void setObject(SoccerObject object) {
	// this.object = object;
	// }

	/**
	 * This method retrieves the position of this object!
	 */
	public Point2f getPosition() {
		return this.position;
	}

	/**
	 * This method sets a new position to the object
	 * 
	 * @param position -
	 *            the new position
	 */
	public void setPosition(Point2f position) {
		this.position = position;
	}

	/**
	 * This method retrieves the speed-angle, i.e. the direction, the speed has
	 * to be.
	 * 
	 * @return - the speeds direction
	 */
	public float getSpeed_angle() {
		return this.speed_angle;
	}

	/**
	 * This method sets the direction, the speed has to be, ranging from
	 * {-180,180]. Other values will be normalized.
	 * 
	 * @param speed_angle -
	 *            the new speeds angle
	 */
	public void setSpeed_angle(float speed_angle) {
		this.speed_angle = (float) Vektor.normalize(speed_angle);
	}

	/**
	 * This method retrieves the speeds angle diffusion, which is a cone with
	 * center-angle <speed_angle>. This cone defines the angles, which are also
	 * possible for speed. The values range from [0..180]. Note, that a value of
	 * 45 will create a 90 degree cone, because the diffusion will be added to
	 * both sides!
	 * 
	 * @return the diffusion of the speed
	 */
	public float getSpeed_angle_diff() {
		return this.speed_angle_diff;
	}

	/**
	 * This method sets the speeds angle diffusion, which is a cone with
	 * center-angle <speed_angle>. This cone defines the angles, which are also
	 * possible for speed. The values range from [0..180]. Note, that a value of
	 * 45 will create a 90 degree cone, because the diffusion will be added to
	 * both sides!
	 * 
	 * @param the
	 *            new diffusion of the speed - which will be changed if values
	 *            are not allowed, so that, the diffusion ranges between
	 *            [0..180]
	 */
	public void setSpeed_angle_diff(float speed_angle_diff) {
		this.speed_angle_diff = Math.min(180, speed_angle_diff);
		this.speed_angle_diff = Math.max(0, speed_angle_diff);
	}

	/**
	 * This method retrieves the positioning type, i.e. if the position has a
	 * fixed value (FIXED), is inside of a rectangular space (RECTANGULAR) or
	 * insode a circle (CIRCULAR)
	 * 
	 * @return - the active position-type
	 */
	public POSITION_TYPE getType() {
		return this.type;
	}

	/**
	 * This method sets the positioning type, i.e. if the position has a fixed
	 * value (FIXED), is inside of a rectangular space (RECTANGULAR) or insode a
	 * circle (CIRCULAR)
	 * 
	 * @param type -
	 *            the new type
	 */
	public void setType(POSITION_TYPE type) {
		this.type = type;
	}

	/**
	 * This method retreives the x-position diffusion, which should be always
	 * positive. The real diffusion affects both sides, so that the opened space
	 * is twice as large.
	 * 
	 * @return - the x-diffusion
	 */
	public float getX_pos_diff() {
		return this.x_pos_diff;
	}

	/**
	 * This method sets a new x-diffusion. This value should be always positive.
	 * The created space will be in case of RECTANGULAR positions: [position.x -
	 * x_pos_diff, position.x - x_pos_diff] When the positioning-type is
	 * Circular, this value is used as the x-radius of a oval shape. Fixed
	 * positioning ignores this value.
	 * 
	 * @param x_pos_diff -
	 *            the new x-diffusion position value
	 */
	public void setX_pos_diff(float x_pos_diff) {
		this.x_pos_diff = x_pos_diff;
	}

	/**
	 * This method retreives the y-position diffusion, which should be always
	 * positive. The real diffusion affects both sides, so that the opened space
	 * is twice as large.
	 * 
	 * @return - the y-diffusion
	 */
	public float getY_pos_diff() {
		return this.y_pos_diff;
	}

	/**
	 * This method sets a new y-diffusion. This value should be always positive.
	 * The created space will be in case of RECTANGULAR positions: [position.y -
	 * y_pos_diff, position.y - y_pos_diff] When the positioning-type is
	 * Circular, this value is used as the y-radius of a oval shape. Fixed
	 * positioning ignores this value.
	 * 
	 * @param y_pos_diff -
	 *            the new y-diffusion position value
	 */
	public void setY_pos_diff(float y_pos_diff) {
		this.y_pos_diff = y_pos_diff;
	}

	/**
	 * This method sets a point, which maps from the mouse- position of the
	 * trainingsbutton to the soccer-field.
	 * 
	 * @param observer -
	 *            the point, which is selected on the field, when having the
	 *            mouse over the trainingsbutton
	 */
	public void setObservePoint(Point2f observer) {
		this.observer = observer;
	}

	/**
	 * This method retreives the observer-button!
	 * 
	 * @return
	 */
	public Point2f getObservePoint() {
		return this.observer;
	}

	/**
	 * This method retrieves a trainer-action, that will satisfy the state of
	 * the specified attributes. Executing it will cause the object to have the
	 * desired position, speed, etc.
	 * 
	 * @param left
	 * @return a trainer action
	 */
	public CoachAction getTrainerAction(int i, Team team) {
		CoachAction ca = null;

		// position and speed are general:
		Point2f pos = this.generatePosition();
		Point2f spd = this.generateSpeed();

		if (this instanceof PlayerTrainingModel) {
			// identify the team-side:
			int num = (i < 11) ? i + 1 : i - 10;
			char side = (i < 11) ? 'l' : 'r';
			if (team == null)
				return null;

			// check, if the player is active
			PlayerTrainingModel ptm = (PlayerTrainingModel) this;
			if (!ptm.isActive()) {
				ca = new CoachAction("(kill_player " + side + " " + num + ")");
				ca.setType(Action.TYPE.COACH_KILL_PLAYER);
				return ca;
			}

			// determine his body-angle
			float bAngle = this.generateBodyAngle(ptm);

			// check, if the speed should be aligned with body-angle
			if (ptm.getAlignSpeedWithBody())
				spd = this.generateSpeed(bAngle);

			// create the coach-action and return the string
			ca = new CoachAction(num, team.name, pos.y, pos.x, bAngle, spd.y,
					spd.x);
			return ca;
		} else {

			// create the coach-action and return the string
			ca = new CoachAction(pos.y, pos.x, spd.y, spd.x);
			return ca;
		}
	}

	/**
	 * This method generates a body angle, as specified in the trainingModel of
	 * the given player
	 * 
	 * @param ptm -
	 *            the playerTrainingModel
	 * @return a body angle
	 */
	private float generateBodyAngle(PlayerTrainingModel ptm) {
		Random r = new Random();
		float angle = ptm.getBody_angle();
		float off = r.nextFloat() * ptm.getBody_angle_diff();
		if (r.nextBoolean())
			off *= -1;
		angle += off;
		return angle;
	}

	/**
	 * This method generates a position out of the object-model. The position
	 * depends on the type (FIXED, RECTANGULAR, CIRCLE) and the specified data.
	 */
	public Point2f generatePosition() {
		Point2f pos = new Point2f();
		if (this.type == POSITION_TYPE.FIXED) {
			pos.set(this.position);
		} else if (this.type == POSITION_TYPE.RECTANGULAR) {
			Random r = new Random();
			float xr = r.nextFloat() * this.x_pos_diff;
			if (r.nextBoolean())
				xr *= -1;
			float yr = r.nextFloat() * this.y_pos_diff;
			if (r.nextBoolean())
				yr *= -1;
			pos.set(this.position);
			pos.x += yr;
			pos.y += xr;
		} else if (this.type == POSITION_TYPE.CIRCULAR) {
			Random r = new Random();
			float angle = r.nextFloat() * 360;
			float xps = (this.x_pos_diff * (float) Math.cos(Math
					.toRadians(angle)));
			float yps = (this.y_pos_diff * (float) Math.sin(Math
					.toRadians(angle)));
			Vektor v = new Vektor(Vektor.XY, xps, yps);
			v.setLength(v.getLength() * r.nextFloat());
			pos.set(this.position);
			pos.x += v.y;
			pos.y += v.x;
		}
		return pos;
	}

	/**
	 * This method generates a speed from the specified values, including
	 * randomnes, if specified.
	 * 
	 * @return a tuple representing the speed
	 */
	public Point2f generateSpeed() {
		Point2f spd = new Point2f();
		float spd_max = (this instanceof PlayerTrainingModel) ? 1.2f : 2.7f;

		Random r = new Random();
		// determine the speed-angle:
		float randAngle = r.nextFloat() * this.speed_angle_diff;
		if (r.nextBoolean())
			randAngle *= -1;
		randAngle = this.speed_angle + randAngle;

		// determine speed-length:
		float spdlen = ((float) (this.max_speed - this.min_speed))
				* r.nextFloat();
		spdlen += this.min_speed;
		spdlen *= (0.01 * spd_max);
		Vektor v = new Vektor(spdlen, randAngle);
		spd.x = (float) v.y;
		spd.y = (float) v.x;
		return spd;
	}

	/**
	 * This method generates a speed from the specified values, including
	 * randomnes, if specified. The angle of the speed is defined by body-angle!
	 * 
	 * @return a tuple representing the speed
	 */
	public Point2f generateSpeed(float bAngle) {
		Point2f spd = new Point2f();
		float spd_max = (this instanceof PlayerTrainingModel) ? 1.2f : 2.7f;

		Random r = new Random();

		// determine speed-length:
		float spdlen = ((float) (this.max_speed - this.min_speed))
				* r.nextFloat();
		spdlen += this.min_speed;
		spdlen *= (0.01 * spd_max);
		Vektor v = new Vektor(spdlen, bAngle);
		spd.x = (float) v.y;
		spd.y = (float) v.x;
		return spd;
	}

}
